<?php
/**
* This file belongs to the core webappkit package (kit)
* @link http://webappkit.net
* @package webappkit
* @author J.Ducastel <jeremie@ducastel.name>
* @license http://opensource.org/licenses/gpl-license.php GNU Public License
*/


if (class_exists('webappkit'))
	return;

/**
* webappkit "kit" manipulation class.
* This is the very core class for webappkit packaging system.
*/
class webappkit {

	/**
	* @var string unique kit id
	*/
	var $id;
	
	/**
	* @var bool has resources been loaded ?
	*/
	var $loaded=false;
	
	/**
	* @var array loaded php files
	*/
	var $loaded_files=array();

	/**
	* @var string relative path to kit root, including trailing /
	*/
	var $path;
	
	/**
	* @var array metadata / self-building information
	*/
	var $meta=array();
	
	/**
	* @var array provided subkits ids
	*/
	var $subkits=array();
	
	/**
	* @var array services instances
	*/
	var $services=array();

	/**
	 * @var array storages instances
	 */
	var $storages=array();
	
/*--------------------------------------------------------------------
 STATIC METHODS
--------------------------------------------------------------------*/
	
	/**
	* factory : use this to get a kit instance.
	* @param string $id kit id : use / to access subkits
	* @static
	* @return webappkit
	*/
	function & getKit($id) {
		// 
		static $kits=array();
		if (!isset($kits[$id])) {
			// trying to build
			$kits[$id]=new webappkit($id);
		}
		return $kits[$id];
	}/**/
	
	/**
	* keeps track of loaded kits
	* @access static
	* @param string $id to add a kits to the list
	*/
	function loadedKits($id=null) {
		static $kits=array();
		// have to add an item ? checking
		if ($id and $kit=&webappkit::getkit($id) and $kit->isLoaded()) {
			$kits[$id]=&$kit;
		}
		return $kits;
	}/**/
	
	/**
	* loads and returns a kit
	* @static
	* @param string $id
	* @return webappkit or false
	*/
	function & loadKit($id) {
		// getting instance
		if (!$kit=&webappkit::getKit($id)) {
			$f=false;
			return $f;
		}
		$kit->load();
		return $kit;
		//if (!$this->)
	}
	
	/**
	* get a kit instance, then include its wak.run.php file if present
	* @static
	* @param string $id
	*/
	function runKit($id) {
		if (!$kit=webappkit::getkit($id))
			return false;
		return $kit->run();
	}/**/
	
	/**
	* return all root kit instances
	* 
	* @static
	* @return array id => instance
	*/
	function getKits($flat=false) {
		// listing root kits
		$roots=webappkit::_kitsInPath(WAK_ROOT_PATH);
		$list=array();
		foreach ($roots as $id) {
			$kit=&webappkit::getkit($id);//  echo $kit->getid();
			$list[$id]=$kit;
		}
		// going recursive ?
		if ($flat) {
			// looping subkits
			foreach (array_keys($list) as $id) {
				// have they subkits too ?
				if ($sublist=$list[$id]->getSubKits(true)) {
					// pushing grandchildren
					// echo '<pre>'; print_r($sublist); echo '</pre>';
					// $list=array_merge($list,$sublist);
					// echo '<pre>'; print_r($newlist); echo '</pre>';
				}
			}
		}
		return $list;
	}/**/
	
	/**
	* checks/cleans kit ID
	* @static
	* @param string
	* @return stringor false if id is invalid
	*/
	function checkID($id) {
		$id=strtolower($id);
		if (!ereg('^[a-z][a-z0-9_]*(/[a-z][a-z0-9_]*)*$',$id))
			return false;
		return $id;
	}/**/
	
	/**
	* returns spent time in microseconds, with chosen precision
	* if not defined, defines WAK_START_TIME as reference microtime
	* @param int $precision (default 5) 
	* @static
	* @access public
	* @return float spent time since the first call
	*/
	function timeCounter($precision=5) {
		list($usec, $sec) = explode(" ", microtime());
		$microtime=((float)$usec + (float)$sec);
		if (!defined('WAK_START_TIME'))
			define('WAK_START_TIME',$microtime);
		//echo $microtime;
		$timeCounter=$microtime-WAK_START_TIME;
		return round($timeCounter,$precision);
	}/**/
	
	/**
	* totalize weight for loaded php files
	* /
	function phpWeightCounter($add=0) {
	
	}/**/
	
	/**
	* constructor alias. use factory static method getkit() instead.
	* @access private
	* @param string $id
	*/
	function webappkit($id) {
		return $this->__construct($id);
	}/**/
	
	/**
	* constructor. use factory static method getkit() instead.
	* @access private
	* @param string $id
	*/
	function __construct($id) {
		if (!$this->id=webappkit::checkId($id)) {
			trigger_error("invalid kit id $id"); // exit;
		}
		// getting path from id
		if (!$this->path=$this->_pathFromId($id)) {
			trigger_error("webappkit: can't build kit ".$id,E_USER_WARNING);
		}
		// reading ini
		$this->_loadIni();
		// listing subkits
		$this->_listSubKits();
	}/**/
	
/*--------------------------------------------------------------------
PUBLIC INSTANCE METHODS
--------------------------------------------------------------------*/
	
	/**
	* loads kits resources
	* @access public
	* @return bool
	*/
	function load() {
		if (!$this->loaded) {
			// loads external/sub kits (dependancies)
			$this->_loadKits();
			// autoloading own resources
			$this->_loadFiles();
			$this->_loadSubKits();
			// including init script if present
			$this->_init();
			// registering as loaded
			$this->loaded=true;
			webappkit::loadedKits($this->id);
		}
		return $this->loaded;
	}/**/
	
	/**
	* has been loaded ?
	* @access public
	* @return bool
	*/
	function isLoaded() {
		return $this->loaded;
	}/**/
	
	/**
	* totalize weight of loaded php files
	* @access public
	* @return int
	*/
	function loadedWeight() {
		$weight=0;
		foreach ($this->loaded_files as $path)
			$weight+=filesize($path);
		return $weight;
	}/**/
	
	/**
	* executes self wak.run.php file if present.
	* @access public
	*/
	function run() {
		$runfile=$this->path.'wak.run.php';
		if (!file_exists($runfile)) {
			trigger_error("no run file for kit {$this->id}",E_USER_ERROR);
			return false;
		}
		return include($runfile);
	}/**/
	
	/**
	* return kit's id
	* @access public
	* @return string
	*/
	function getID() {
		return $this->id;
	}/**/
	
	/**
	* return kit's root path
	* @access public
	* @return string
	*/
	function getPath() {
		return $this->path;
	}/**/
	
	/**
	* return kit short description
	* @access public
	* @return string
	*/
	function getDescription() {
		if (!isset($this->meta['kit']['desc']))
			return false;
		return $this->meta['kit']['desc'];
	}/**/
	
	/**
	* @access public
	* @return string
	*/
	function getVersion() {
		if (!isset($this->meta['kit']['version']))
			return false;
		return $this->meta['kit']['version'];
	}/**/
	
	/**
	* lists own subkits
	* @acces public
	* @return array
	*/
	function listSubKits() {
		return $this->subkits;
	}/**/
	
	/**
	* return subkits instances
	*/
	function getSubKits($flat=true) {
		// building list
		$list=array();
		// getting direct subkits
		foreach ($this->subkits as $id)
			// getting instances
			$list[$id]=&webappkit::getkit($this->id.'/'.$id);
		// going recursive ?
		if ($flat) {
			// looping subkits
			foreach (array_keys($list) as $id) {
				// have they subkits too ?
				if ($sublist=$list[$id]->getSubKits(true)) {
					// pushing grandchildren
					//echo '<pre>'; print_r($sublist); echo '</pre>';
					$list=array_merge($list,$sublist);
					// echo '<pre>'; print_r($newlist); echo '</pre>';
				}
			}
		}
		return $list;
	}/**/

	/**
	 * checks if kit provide some service
	 * @access public
	 * @param string $name
	 * @return bool
	 */
	function hasService($name) {
		return isset($this->meta['services'][$name]);
	}/**/
	
	/**
	* get named service instance from kit.
	* @access public
	* @param string $name
	* @return wakService|false
	*/
	function & getService($name) {
		// trying to build service if necessary
		if (!isset($this->services[$name])
		and !$this->_buildService($name)) {
			$f=false;
			return $f;
		}
		return $this->services[$name];
	}/**/

	/**
	 * checks if kit provide some storage
	 * @access public
	 * @param string $name
	 * @return bool
	 */
	function hasStorage($name) {
		return isset($this->meta['storages'][$name]);
	}/**/

	/**
	* get named storage instance from kit.
	* @access public
	* @param string $name
	* @return wakStorage|false
	*/
	function & getStorage($name) {
		// trying to build service if necessary
		if (!isset($this->storages[$name])
		and !$this->_buildStorage($name)) {
			$f=false;
			return $f;
		}
		return $this->storages[$name];
	}/**/
	
	/**
	* lists provided services.
	* @access public
	* @return array
	*/
	function listServices() {
		return array_keys($this->meta['services']);
	}/**/

	/**
	* lists provided storages.
	* @access public
	* @return array
	*/
	function listStorages() {
		return array_keys($this->meta['storages']);
	}
	
	/**
	*
	* /
	function getSubKit($id) {
		
	}/**/

/*--------------------------------------------------------------------
PRIVATE / PROTECTED METHODS
--------------------------------------------------------------------*/
	
	/**
	* sets own path
	* @static
	* @access protected
	* @param string $id
	* @return string path with trailing slash
	*/
	function _pathFromId($id) {
		$levels=explode('/',$id);
		$path='';
		foreach ($levels as $l)
			$path.="$l.wak/";
		$path=WAK_ROOT_PATH.$path;
		if (!file_exists($path)) {
			trigger_error("invalid path {$path} for id {$id}");
			return false;
		}
		return $path;
	}
	
	
	/**
	* loads package metadata from ini file
	* @access protected
	* @return bool
	*/
	function _loadIni() {
		// getting ini file
		$file=$this->path.'wak.ini';
		if (!file_exists($file) or !is_file($file)) {
			trigger_error("kit metadata file $file not found");
			return false;
		}
			//throw new Exception("package info file $file not found");
		// parsing
		if (!$this->meta=parse_ini_file($file,true)) {
			//throw new Exception("failed to parse $file");
			trigger_error("failed to parse $file");
			return false;
		}
		// echo '<pre>'; print_r($this->info);
		//return $this->meta?true:false;
		$this->loaded_files[]=$file;
		return true;
	}/**/
	
	/**
	* loads external kit dependancies
	* @access protected
	* @return bool
	*/
	function _loadKits() {
		if (isset($this->meta['load_kits'])) {
			foreach ($this->meta['load_kits'] as $kit) {
				if (!$this->external_kits[$kit]=&webappkit::loadkit($kit)) {
					return false;
				}
			}
		}
		return true;
	}/**/
	
	/**
	* loads files in INI list [load_files]
	* @access protected
	*/
	function _loadFiles() {
		// first, loads specified own files
		if (isset($this->meta['load_files'])) {
			foreach ($this->meta['load_files'] as $file) {
				if (!$this->_includePhp($file)) {
					return false; //echo "<p>$file</p>";
				}
			}
		}
		return true;
	}/**/
	
	/**
	* lists own subkits.
	* @access protected
	* @return bool
	*/
	function _listSubKits() {
		if (!$this->subkits)
			$this->subkits=$this->_kitsInPath($this->path);
		return true;
	}
	
	/**
	* loads subkits in INI list [load_subkits]
	* @access protected
	* @return bool
	*/
	function _loadSubKits() {
		if (isset($this->meta['load_subkits'])) {
			foreach ($this->meta['load_subkits'] as $kit_id) {
				if (!$subkit=&webappkit::loadkit($this->id.'/'.$kit_id))
					return false;
				//$this->subkits[$kit_id]=&$subkit;
			}
		}
		return true;
	}/**/
	
	/**
	* loads self init file if present.
	* @access protected
	*/
	function _init() {
		$file=$this->path.'wak.init.php';
		if (file_exists($file))
			include($file);
	}/**/
	
	/**
	* includes php library file
	* if file name ends by .php*, tries to load .php4 or .php5 current php version
	* @param string $file 
	* @return bool
	*/
	function _includePhp($file) {
		// auto php version ?
		if (substr($file,-1,1)=='*') {
			$php=substr(phpversion(),0,1);
			$file=substr($file,0,-1).$php;
		}
		// adding self path
		$file=$this->path.$file;
		if (!file_exists($file)) {
			trigger_error("can't include file $file");
			return false;
		}
		$this->loaded_files[]=$file;
		include($file);
		return true;
	}/**/
	
	/**
	* builds service instance
	* @param string $name
	* @access private
	* @return wakService
	*/
	function _buildService($name) {
		// loading self resources if necessary
		$this->load();
		// searching for the service class
		if (!isset($this->meta['services'][$name])) {
			// not referenced
			trigger_error("service $name is not provided by kit {$this->id}",E_USER_WARNING);
			return false;
		}
		$class=$this->meta['services'][$name];
		if (!class_exists($class)) {
			trigger_error("class $class is missing to instanciate service $name from kit {$this->id}.",E_USER_WARNING);
			return false;
		}
		$this->services[$name]=new $class($this,$name);
		return true;
	}/**/

	/**
	* builds storage instance
	* @param string $name
	* @access private
	* @return wakStorage
	*/
	function _buildStorage($name) {
		// loading self resources if necessary
		$this->load();
		// searching for the service class
		if (!isset($this->meta['storages'][$name])) {
			// not referenced
			trigger_error("storage $name is not provided by kit {$this->id}",E_USER_WARNING);
			return false;
		}
		$class=$this->meta['storages'][$name];
		if (!class_exists($class)) {
			trigger_error("class $class is missing to instanciate storage $name from kit {$this->id}.",E_USER_WARNING);
			return false;
		}
		$this->storages[$name]=new $class($this,$name);
		return true;
	}/**/
	
	/**
	* lists kits in a path
	* @static
	* @param string $path
	* @return array
	*/
	function _kitsInPath($path) {
		// listing own files
		$d=dir($path); $kits=array(); //echo '<p>path='.$path;
		while ($file=$d->read()) {
			// keeping all dirs ending by ".wak"
			if (is_dir($path.$file) and (substr($file,-4,4)=='.wak'))
				$kits[]=substr($file,0,-4);
		}
		//print_r($kits);
		return $kits;
	}/**/
}
?>